using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using DanielSig.Sorcery;

namespace DanielSig.UnityExtensions
{
	public static class XComponent
	{
		#region Request Component
		public static Component RequestComponent(this Component comp, Type componentType)
		{
			if(!comp) return null;
			Component requested = comp.GetComponent(componentType);
			if(!requested) requested = comp.gameObject.AddComponent(componentType);
			return requested;
		}
		public static T RequestComponent<T>(this Component comp) where T:Component
		{
			if(!comp) return null;
			T requested = comp.GetComponent<T>();
			if(!requested) requested = comp.gameObject.AddComponent<T>();
			return requested;
		}
		#endregion
		
		#region Local Requests
		#region Special Requests
		public static T RequestCollection<T>(this Component comp, String methodName) where T:class, ICollection, new()
		{
			T collection = new T();
			comp.SendMessage(methodName, collection, SendMessageOptions.RequireReceiver);
			return collection;
		}
		public static void ApplyModification<T>(this Component comp, String methodName, ref T original) where T:struct
		{
			Value<T> val = new Value<T>();
			val.value = original;
			comp.SendMessage(methodName, val, SendMessageOptions.DontRequireReceiver);
			original = val.value;
		}
		public static T RequestBest<T>(this Component comp, String methodName)
		{
			Value<Value<T, float>> val = new Value<Value<T, float>>();
			comp.SendMessage(methodName, val, SendMessageOptions.RequireReceiver);
			return val.value.value;
		}
		public static T RequestBest<T, ParamType>(this Component comp, String methodName, ParamType param)
		{
			Value<Value<T, float>, ParamType> val = new Value<Value<T, float>, ParamType>();
			val.param = param;
			comp.SendMessage(methodName, val, SendMessageOptions.RequireReceiver);
			return val.value.value;
		}
		#endregion
		
		#region No Parameter
		public static T Request<T>(this Component comp, String methodName)
		{
			Value<T> val = new Value<T>();
			comp.SendMessage(methodName, val, SendMessageOptions.RequireReceiver);
			return val.value;
		}
		public static T Request<T>(this Component comp, String methodName, T defaultReturnValue)
		{
			Value<T> val = new Value<T>();
			if(typeof(T).IsClass) val.value = defaultReturnValue;
			comp.SendMessage(methodName, val, SendMessageOptions.DontRequireReceiver);
			if( val.value.Equals(default(T)) ) return defaultReturnValue;
			return val.value;
		}
		#endregion
		
		#region With Parameter
		public static T Request<T, ParamType>(this Component comp, String methodName, ParamType param)
		{
			Value<T, ParamType> val = new Value<T, ParamType>();
			val.param = param;
			comp.SendMessage(methodName, val, SendMessageOptions.RequireReceiver);
			return val.value;
		}
		public static T Request<T, ParamType>(this Component comp, String methodName, ParamType param, T defaultReturnValue)
		{
			Value<T, ParamType> val = new Value<T, ParamType>();
			val.param = param;
			if(typeof(T).IsClass) val.value = defaultReturnValue;
			comp.SendMessage(methodName, val, SendMessageOptions.DontRequireReceiver);
			if( val.value.Equals(default(T)) ) return defaultReturnValue;
			return val.value;
		}
		#endregion
		#endregion
		
		#region Request Upwards
		#region Special Requests
		public static T BegForCollection<T>(this Component comp, String methodName) where T:class, ICollection, new()
		{
			T collection = new T();
			comp.SendMessageUpwards(methodName, collection, SendMessageOptions.RequireReceiver);
			return collection;
		}
		public static void BegForModification<T>(this Component comp, String methodName, ref T original) where T:struct
		{
			Value<T> val = new Value<T>();
			val.value = original;
			comp.SendMessageUpwards(methodName, val, SendMessageOptions.DontRequireReceiver);
			original = val.value;
		}
		public static T BegForBest<T>(this Component comp, String methodName)
		{
			Value<Value<T, float>> val = new Value<Value<T, float>>();
			comp.SendMessageUpwards(methodName, val, SendMessageOptions.RequireReceiver);
			return val.value.value;
		}
		public static T BegForBest<T, ParamType>(this Component comp, String methodName, ParamType param)
		{
			Value<Value<T, float>, ParamType> val = new Value<Value<T, float>, ParamType>();
			val.param = param;
			comp.SendMessageUpwards(methodName, val, SendMessageOptions.RequireReceiver);
			return val.value.value;
		}
		#endregion
		
		#region No Parameter
		public static T BegFor<T>(this Component comp, String methodName)
		{
			Value<T> val = new Value<T>();
			comp.SendMessageUpwards(methodName, val, SendMessageOptions.RequireReceiver);
			return val.value;
		}
		public static T BegFor<T>(this Component comp, String methodName, T defaultReturnValue)
		{
			Value<T> val = new Value<T>();
			if(typeof(T).IsClass) val.value = defaultReturnValue;
			comp.SendMessageUpwards(methodName, val, SendMessageOptions.DontRequireReceiver);
			if( val.value.Equals(default(T)) ) return defaultReturnValue;
			return val.value;
		}
		#endregion
		
		#region With Parameter
		public static T BegFor<T, ParamType>(this Component comp, String methodName, ParamType param)
		{
			Value<T, ParamType> val = new Value<T, ParamType>();
			val.param = param;
			comp.SendMessageUpwards(methodName, val, SendMessageOptions.RequireReceiver);
			return val.value;
		}
		public static T BegFor<T, ParamType>(this Component comp, String methodName, ParamType param, T defaultReturnValue)
		{
			Value<T, ParamType> val = new Value<T, ParamType>();
			val.param = param;
			if(typeof(T).IsClass) val.value = defaultReturnValue;
			comp.SendMessageUpwards(methodName, val, SendMessageOptions.DontRequireReceiver);
			if( val.value.Equals(default(T)) ) return defaultReturnValue;
			return val.value;
		}
		#endregion
		#endregion
		
		#region Request Downwards
		#region Special Requests
		public static T DemandCollection<T>(this Component comp, String methodName) where T:class, ICollection, new()
		{
			T collection = new T();
			comp.BroadcastMessage(methodName, collection, SendMessageOptions.RequireReceiver);
			return collection;
		}
		public static void DemandModification<T>(this Component comp, String methodName, ref T original) where T:struct
		{
			Value<T> val = new Value<T>();
			val.value = original;
			comp.BroadcastMessage(methodName, val, SendMessageOptions.DontRequireReceiver);
			original = val.value;
		}
		public static T DemandBest<T>(this Component comp, String methodName)
		{
			Value<Value<T, float>> val = new Value<Value<T, float>>();
			comp.BroadcastMessage(methodName, val, SendMessageOptions.RequireReceiver);
			return val.value.value;
		}
		public static T DemandBest<T, ParamType>(this Component comp, String methodName, ParamType param)
		{
			Value<Value<T, float>, ParamType> val = new Value<Value<T, float>, ParamType>();
			val.param = param;
			comp.BroadcastMessage(methodName, val, SendMessageOptions.RequireReceiver);
			return val.value.value;
		}
		#endregion
		
		#region No Parameter
		public static T Demand<T>(this Component comp, String methodName)
		{
			Value<T> val = new Value<T>();
			comp.BroadcastMessage(methodName, val, SendMessageOptions.RequireReceiver);
			return val.value;
		}
		public static T Demand<T>(this Component comp, String methodName, T defaultReturnValue)
		{
			Value<T> val = new Value<T>();
			if(typeof(T).IsClass) val.value = defaultReturnValue;
			comp.BroadcastMessage(methodName, val, SendMessageOptions.DontRequireReceiver);
			if( val.value.Equals(default(T)) ) return defaultReturnValue;
			return val.value;
		}
		#endregion
		
		#region With Parameter
		public static T Demand<T, ParamType>(this Component comp, String methodName, ParamType param)
		{
			Value<T, ParamType> val = new Value<T, ParamType>();
			val.param = param;
			comp.BroadcastMessage(methodName, val, SendMessageOptions.RequireReceiver);
			return val.value;
		}
		public static T Demand<T, ParamType>(this Component comp, String methodName, ParamType param, T defaultReturnValue)
		{
			Value<T, ParamType> val = new Value<T, ParamType>();
			val.param = param;
			if(typeof(T).IsClass) val.value = defaultReturnValue;
			comp.BroadcastMessage(methodName, val, SendMessageOptions.DontRequireReceiver);
			if( val.value.Equals(default(T)) ) return defaultReturnValue;
			return val.value;
		}
		#endregion
		#endregion
		
		#region Other
		public static Rigidbody GetBody(this Component comp)
		{
			return (comp.collider ? comp.collider.attachedRigidbody : null) ?? comp.rigidbody;
		}
		public static T TryGet<T>( this Component comp ) where T : Component
		{
			if(comp) return comp.GetComponent<T>();
			return null;
		}
		
		#region Lambda
		#region Local
		public static Component GetComponent(this Component comp, Func<Component, bool> condition)
		{
			Component[] components = comp.GetComponents<Component>();
			foreach (Component c in components)
			{
				if (condition(c))
				{
					return c;
				}
			}
			return null;
		}
		public static T GetComponent<T>(this Component comp, Func<T, bool> condition) where T:Component
		{
			T[] components = comp.GetComponents<T>();
			foreach (T c in components)
			{
				if (condition(c))
				{
					return c;
				}
			}
			return null;
		}
		public static Component[] GetComponents(this Component comp, Func<Component, bool> condition)
		{
			Component[] components = comp.GetComponents<Component>();
			int count = 0;
			int length = components.Length;
			for(int i = 0; i < length; i++)
			{
				Component c = components[i];
				if (condition(c))
				{
					components[count++] = c;
				}
			}
			Array.Resize<Component>(ref components, count);
			return components;
		}
		public static T[] GetComponents<T>(this Component comp, Func<T, bool> condition) where T:Component
		{
			T[] components = comp.GetComponents<T>();
			int count = 0;
			int length = components.Length;
			for(int i = 0; i < length; i++)
			{
				T c = components[i];
				if (condition(c))
				{
					components[count++] = c;
				}
			}
			Array.Resize<T>(ref components, count);
			return components;
		}
		#endregion
		#region Children
		public static Component GetComponentInChildren(this Component comp, Func<Component, bool> condition)
		{
			Component[] components = comp.GetComponentsInChildren<Component>();
			foreach (Component c in components)
			{
				if (condition(c))
				{
					return c;
				}
			}
			return null;
		}
		public static T GetComponentInChildren<T>(this Component comp, Func<T, bool> condition) where T:Component
		{
			T[] components = comp.GetComponentsInChildren<T>();
			foreach (T c in components)
			{
				if (condition(c))
				{
					return c;
				}
			}
			return null;
		}
		public static Component[] GetComponentsInChildren(this Component comp, Func<Component, bool> condition)
		{
			Component[] components = comp.GetComponentsInChildren<Component>();
			int count = 0;
			int length = components.Length;
			for(int i = 0; i < length; i++)
			{
				Component c = components[i];
				if (condition(c))
				{
					components[count++] = c;
				}
			}
			Array.Resize<Component>(ref components, count);
			return components;
		}
		public static T[] GetComponentsInChildren<T>(this Component comp, Func<T, bool> condition) where T:Component
		{
			T[] components = comp.GetComponentsInChildren<T>();
			int count = 0;
			int length = components.Length;
			for(int i = 0; i < length; i++)
			{
				T c = components[i];
				if (condition(c))
				{
					components[count++] = c;
				}
			}
			Array.Resize<T>(ref components, count);
			return components;
		}
		#endregion
		#endregion
		
		public static T GetComponentByInterface<T>(this Component comp)
		{
			Component[] components = comp.GetComponents<Component>();
			foreach (Component c in components)
			{
				if (c is T)
				{
					return (T)(object)c;
				}
			}
			return default(T);
		}
		public static T[] GetComponentsByInterface<T>(this Component comp)
		{
			Component[] components = comp.GetComponents<Component>();
			LinkedList<T> results = new LinkedList<T>();
			
			foreach (Component c in components)
			{
				if (c is T)
				{
					results.AddLast((T)(object)c);
				}
			}
			return results.ToArray();
		}
		public static T GetComponentInChildrenByInterface<T>(this Component comp, bool includeInactive = false)
		{
			Component[] components = comp.GetComponentsInChildren<Component>(includeInactive);
			foreach (Component c in components)
			{
				if (c is T)
				{
					return (T)(object)c;
				}
			}
			return default(T);
		}
		public static T[] GetComponentsInChildrenByInterface<T>(this Component comp)
		{
			Component[] components = comp.GetComponentsInChildren<Component>();
			LinkedList<T> results = new LinkedList<T>();
			
			foreach (Component c in components)
			{
				if (c is T)
				{
					results.AddLast((T)(object)c);
				}
			}
			return results.ToArray();
		}
		public static SortedLinkedList<T> GetComponentsByInterfaceSorted<T>(this Component comp)
		{
			Component[] components = comp.GetComponents<Component>();
			SortedLinkedList<T> results = new SortedLinkedList<T>();
			
			foreach (Component c in components)
			{
				if (c is T)
				{
					results.Add((T)(object)c);
				}
			}
			return results;
		}
		public static void SendMessageDelayed(this Component comp, string messageName, float delayInSeconds, object value, SendMessageOptions options = SendMessageOptions.RequireReceiver)
		{
			comp.CallDelayed("SendMessage", delayInSeconds, messageName, value, options);
		}
		public static void SendMessageDelayed(this Component comp, string messageName, float delayInSeconds, SendMessageOptions options = SendMessageOptions.RequireReceiver)
		{
			comp.CallDelayed("SendMessage", delayInSeconds, messageName, options);
		}
		#endregion
	}
	public class Value<T>
	{
		public T value;
	}
	public class Value<T, ParamType>
	{
		public T value;
		public ParamType param;
	}
}

